/**
 * 
 */
package com.feib.stms.security.web.authentication;

import java.io.IOException;
import java.util.Date;

import javax.security.auth.login.AccountExpiredException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.feib.stms.model.Group;
import com.feib.stms.model.User;
import com.feib.stms.model.UserLoginHistory;
import com.feib.stms.security.captcha.CaptchaAuthenticationException;
import com.feib.stms.security.core.userdetails.StmsUserDetailService;
import com.feib.stms.service.UserLoginHistoryService;
import com.feib.stms.service.UserService;

/**
 * @author Jimmy Liu
 *
 */
public class StmsAuthenticationFailureHandler extends
		SimpleUrlAuthenticationFailureHandler 
{
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    public static final String LAST_STMS_FAIL_AUTHENTICATION_USERID = "LAST_STMS_FAIL_AUTHENTICATION_USERID";
    public static final String LAST_STMS_FAIL_AUTHENTICATION_GROUPNO = "LAST_STMS_FAIL_AUTHENTICATION_GROUPNO";
    
    private String usernameParameter = UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY;
    
    private UserLoginHistoryService userLoginHistoryService;
    private UserService userService;
    
    private AuthenticationEntryPoint failureEntryPoint;
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException 
    {
    	if (logger.isDebugEnabled()) {
            logger.debug( "使用者登入失敗事件處理...");
        }
        // 從資料庫中去得使用者資訊
        User user = getUser(request, exception);
        if (null == user)
        {
        	user = createUser(request, exception);
        }
        
        String userId = user.getUserId();
        String groupNo = null;
        if (null != user.getGroup() && null != user.getGroup().getGroupNo())
        	groupNo = user.getGroup().getGroupNo();
        
        int pwdErrorTime = (null == user.getPwdErrorTime() ? 0 : user.getPwdErrorTime().intValue());
        int captchaErrorTime = (null == user.getCaptchaErrorTime() ? 0 : user.getCaptchaErrorTime().intValue());
        boolean forceChgPwd = false;
        
        // 新增使用者登入資訊
    	try {    		
            UserLoginHistory userLoginHistory = new UserLoginHistory();
            
            userLoginHistory.setUser(user);
            // 儲存畫面的密碼
            userLoginHistory.setUserPwd(request.getParameter(passwordParameter));
            // 儲存畫面的驗證碼
            userLoginHistory.setCaptch(request.getParameter("j_captcha_response"));
            HttpSession session = request.getSession(false);
            if (null != session)
            	userLoginHistory.setSessionId(session.getId());
            userLoginHistory.setLoginTime(new Date());
            userLoginHistory.setLoginLocaleIP(request.getRemoteAddr());
            
            userLoginHistory.setSucceed(Boolean.FALSE); // 失敗
            String failedNote = null;        
            Class<? extends AuthenticationException> cls = exception.getClass();
            if (AccountExpiredException.class.isAssignableFrom(cls))
            {
            	// 帳號過期
            	failedNote = "帳號過期";
            }
            else if (CredentialsExpiredException.class.isAssignableFrom(cls))
            {
            	// 密碼過期
            	failedNote = "密碼過期";
            	forceChgPwd = true;
            }
            else if (DisabledException.class.isAssignableFrom(cls))
            {
            	// 帳號尚未啟用, 或是已被取消刪除
            	failedNote = "帳號尚未啟用, 或是已被取消刪除";
            }
            else if (LockedException.class.isAssignableFrom(cls))
            {
            	// 帳號已被鎖住
            	failedNote = "帳號已被鎖住";
            }
            else if (AccountStatusException.class.isAssignableFrom(cls))
            {
            	// 帳號狀態其他問題
            	failedNote = "帳號狀態其他問題";
            }
            else if (BadCredentialsException.class.isAssignableFrom(cls))
            {
            	// 密碼錯誤
            	pwdErrorTime ++;
            	failedNote = "密碼錯誤, 累計錯誤共 " + pwdErrorTime + " 次";
            }
            else if (AuthenticationCredentialsNotFoundException.class.isAssignableFrom(cls))
            {
            	// 密碼錯誤或無資料庫無對應密碼
            	pwdErrorTime ++;
            	failedNote = "密碼錯誤或無資料庫無對應密碼, 累計錯誤共 " + pwdErrorTime + " 次";
            }
            else if (UsernameNotFoundException.class.isAssignableFrom(cls))
            {
            	// 帳號不存在
            	failedNote = "帳號不存在";
            }
            else if (CaptchaAuthenticationException.class.isAssignableFrom(cls))
            {
            	// 驗證碼錯誤
            	captchaErrorTime ++;
            	failedNote = "驗證碼錯誤, 累計錯誤共 " + captchaErrorTime + " 次";
            }
            else if (UserIsActivedAuthenticationException.class.isAssignableFrom(cls))
            {
            	failedNote = "使用者已登入";
            }
            else if (AuthenticationException.class.isAssignableFrom(cls))
            {
            	// 授權認證其他有問題
            	failedNote = "認證資訊有問題";
            }
            userLoginHistory.setFailedNote(failedNote);
            
            userLoginHistoryService.addUserLoginHistory(userLoginHistory);
		} catch (Exception e) {
			logger.warn("使用者登入失敗，新增使用者登入資訊發生錯誤！！", e);
		}
		
		// 更新使用者基本資料
		try {
			user.setPwdErrorTime(pwdErrorTime);
			user.setCaptchaErrorTime(captchaErrorTime);
			
			///密碼累計錯誤 3 次，鎖定帳號";
            if (pwdErrorTime >= 3)
            {
                user.setLocked(Boolean.TRUE);
            }
            
            // 驗證碼累計錯誤 3次，鎖定帳號";
            if (captchaErrorTime >= 3)
            {
                user.setLocked(Boolean.TRUE);
            }
            

    		user.setUpdatedDate(new Date());
    		user.setUpdatedBy(user.getGroup().getGroupNo() +"_" + user.getUserId());
    			
    		
            userService.loginFail(user);
			
		} catch (Exception e) {
			logger.warn("使用者登入失敗，更新使用者密碼與驗證碼錯誤次數資訊發生錯誤！！", e);
		}
		
		if (forceChgPwd)
		{
            saveException(request, exception);
        	request.getSession().setAttribute("__FORECE_CHG_PWD_USER", user);
            this.getRedirectStrategy().sendRedirect(request, response, "/Stms9001.action");
		}
		else
		{
            saveUserIdGroupNo(request, userId, groupNo);
            
			if (null != this.failureEntryPoint)
			{
	            saveException(request, exception);
				this.failureEntryPoint.commence(request, response, exception);
			}			
			else {
				// 繼續其他登入失敗處理
				super.onAuthenticationFailure(request, response, exception);
	        } 
		}
    	
    }
    
    public final void saveUserIdGroupNo(HttpServletRequest request, String userId, String groupNo) {
        if (isUseForward()) {
            request.setAttribute(LAST_STMS_FAIL_AUTHENTICATION_USERID, userId);
            request.setAttribute(LAST_STMS_FAIL_AUTHENTICATION_GROUPNO, groupNo);
        } else {
            HttpSession session = request.getSession(false);

            if (session != null || isAllowSessionCreation()) {
            	request.getSession().setAttribute(LAST_STMS_FAIL_AUTHENTICATION_USERID, userId);
            	request.getSession().setAttribute(LAST_STMS_FAIL_AUTHENTICATION_GROUPNO, groupNo);
            }
        }
    }
    
    protected User getUser(HttpServletRequest request, AuthenticationException exception)
    {
    	String groupNo = null;// 群組代號
        String userId = null; // 使用者帳號
        
        String username = null;
        
        try {
        	username = exception.getAuthentication().getName();
	    	
        	String[] userNameTemp = username.split(StmsUserDetailService.USERNAME_SPLIT_FORMAT);
        	if (2 == userNameTemp.length){
    			groupNo = userNameTemp[0];// 群組代號
    	        userId = userNameTemp[1]; // 使用者帳號
            }
		} catch (Exception e) {
			// skip
		}
		
		if (null == groupNo)
		{
			try {
				username = request.getParameter(usernameParameter);
		    	
				String[] userNameTemp = username.split(StmsUserDetailService.USERNAME_SPLIT_FORMAT);
	        	if (2 == userNameTemp.length){
	    			groupNo = userNameTemp[0];// 群組代號
	    	        userId = userNameTemp[1]; // 使用者帳號
	            }
			} catch (Exception e) {
				// skip
			}
			
		}
		
		if (null == groupNo)
		{
			groupNo = request.getParameter("user.group.groupNo");
			userId = request.getParameter("user.userId");
		}
		
        // 從資料庫中去得使用者資訊
        User user = userService.findByUserIdGroupNo(userId, groupNo);
        
        return user;
    }
    
    protected User createUser(HttpServletRequest request, AuthenticationException exception)
    {
    	String groupNo = null;// 群組代號
        String userId = null; // 使用者帳號
        
        String username = null;
        
        try {
        	username = exception.getAuthentication().getName();
        	String[] userNameTemp = username.split(StmsUserDetailService.USERNAME_SPLIT_FORMAT);
        	if (2 == userNameTemp.length){
    			groupNo = userNameTemp[0];// 群組代號
    	        userId = userNameTemp[1]; // 使用者帳號
            }
		} catch (Exception e) {
			// skip
		}
		
		if (null == groupNo)
		{
			try {
				username = request.getParameter(usernameParameter);
				String[] userNameTemp = username.split(StmsUserDetailService.USERNAME_SPLIT_FORMAT);
	        	if (2 == userNameTemp.length){
	    			groupNo = userNameTemp[0];// 群組代號
	    	        userId = userNameTemp[1]; // 使用者帳號
	            }
			} catch (Exception e) {
				// skip
			}
			
		}
		
		if (null == groupNo)
		{
			groupNo = request.getParameter("user.group.groupNo");
			userId = request.getParameter("user.userId");
		}
		
		if (null == userId)
		{
			if (null != username)
				userId = username;
			else
				userId = "Unknown";
		}
        // 從資料庫中去得使用者資訊
        User user = new User();
        user.setUserId(userId);
        user.setGroup(new Group());
        user.getGroup().setGroupNo(groupNo);
        
        return user;
    }
    
    

	/**
	 * @param passwordParameter 的設定的 passwordParameter
	 */
	public void setPasswordParameter(String passwordParameter) {
		this.passwordParameter = passwordParameter;
	}

	/**
	 * @param userLoginHistoryService 的設定的 userLoginHistoryService
	 */
	public void setUserLoginHistoryService(
			UserLoginHistoryService userLoginHistoryService) {
		this.userLoginHistoryService = userLoginHistoryService;
	}

	/**
	 * @param usernameParameter 的設定的 usernameParameter
	 */
	public void setUsernameParameter(String usernameParameter) {
		this.usernameParameter = usernameParameter;
	}

	/**
	 * @param userService 的設定的 userService
	 */
	public void setUserService(UserService userService) {
		this.userService = userService;
	}

	public AuthenticationEntryPoint getFailureEntryPoint() {
		return failureEntryPoint;
	}

	public void setFailureEntryPoint(AuthenticationEntryPoint failureEntryPoint) {
		this.failureEntryPoint = failureEntryPoint;
	}

}
